## @package karboom.elements
# Extended elements
#
# Extensions of karboom.classes.Element
#

import cairo
from math import sin, cos

from karboom.classes import Element, Pattern
from karboom.utils import interpolate

import karboom.errors

## Matrix
#
# Perform affine matrix transformations.
#
class Matrix(Element):
    
    precedence = 0.0
    
    ## Matrix, in a 6-tuple formatted (xx, yx, xy, yy, x0, y0)
    matrix = (1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
    
    ## Determinant
    #
    # Get the determinant of the matrix.
    #
    def determinant(self):
        return self.matrix[0] * self.matrix[3] - self.matrix[1] * self.matrix[2]
    
    ## Multiply
    #
    # Multiply the matrix by another matrix (overloaded).
    # @param other: Matrix or scalar to multiply by.
    #
    def __mul__(self, other):
        if isinstance(other, (int, long, float, complex)):
            return Matrix(matrix=( self.matrix[0] * other,
                                   self.matrix[1] * other,
                                   self.matrix[2] * other,
                                   self.matrix[3] * other,
                                   self.matrix[4],
                                   self.matrix[5] ))
        
        return Matrix(matrix=( self.matrix[0] * other.matrix[0] + self.matrix[1] * other.matrix[2],
                               self.matrix[0] * other.matrix[1] + self.matrix[1] * other.matrix[3],
                               self.matrix[2] * other.matrix[0] + self.matrix[3] * other.matrix[2],
                               self.matrix[2] * other.matrix[1] + self.matrix[3] * other.matrix[3],
                               self.matrix[4] + other.matrix[4],
                               self.matrix[5] + other.matrix[5] ))
    
    ## Adjugate
    #
    # Get the matrix adjugate.
    #
    def adjugate(self):
        return Matrix(matrix=( self.matrix[3],
                               -self.matrix[1],
                               -self.matrix[2],
                               self.matrix[0],
                               (self.matrix[2] * self.matrix[5] - self.matrix[3] * self.matrix[4]),
                               (self.matrix[1] * self.matrix[4] - self.matrix[0]* self.matrix[5]) ))
    
    
    ## Inverse
    #
    # Get the matrix inverse.
    #
    def inverse(self):
        return self.adjugate() * (1/self.determinant())
        
    @staticmethod
    def __interpolate__(percent, start, end, accel):
        return Matrix(matrix=( interpolate(percent, start.matrix[0], end.matrix[0], accel), interpolate(percent, start.matrix[1], end.matrix[1], accel),
                               interpolate(percent, start.matrix[2], end.matrix[2], accel), interpolate(percent, start.matrix[3], end.matrix[3], accel),
                               interpolate(percent, start.matrix[4], end.matrix[4], accel),
                               interpolate(percent, start.matrix[5], end.matrix[5], accel) ))
    
    def __render__(self):
        self.context.transform(cairo.Matrix( self.matrix[0], self.matrix[1],
                                             self.matrix[2], self.matrix[3],
                                             self.matrix[4],
                                             self.matrix[5]  )) 

## Transform
#
# Base class for transform actions - should not be called directly.
#
class Transform(Element):
    
    precedence = 0.0
    
    ## Matrices
    #
    # Return the matrices for the transformation.
    #
    def __matrices__(self):
        return (Matrix(),)
    
    def __render__(self):
        for m in self.__matrices__():
            m.context, m.layer, m.fps, m.size, m.frame = self.context, self.layer, self.fps, self.size, self.frame
            m.__render__()

## Translate
#
# Translate by matrix transformation.
#
class Translate(Transform):
    
    precedence = -0.5
    
    ## Distance to translate to, formatted (x, y)
    distance = (0.0, 0.0)
    
    @staticmethod
    def __interpolate__(percent, start, end, accel):
        return(Translate(distance=(interpolate(percent, start.distance[0], end.distance[0], accel),
                                   interpolate(percent, start.distance[1], end.distance[1], accel)) ))
        
    def __matrices__(self):
        return (
                Matrix(matrix=( 1.0, 0.0,
                                0.0, 1.0,
                                self.distance[0],
                                self.distance[1] )),
                )
    
    def __render__(self):
        self.layer.position = (self.layer.position[0] + self.distance[0], self.layer.position[1] + self.distance[1])
        Transform.__render__(self)

## Rotate
#
# Rotate by matrix transformation.
#
class Rotate(Transform):
    
    ## Angle to rotate through in radians
    angle = 0
    
    ## Origin, in a 2-tuple formatted (x, y)
    origin = (0.0, 0.0)
        
    @staticmethod
    def __interpolate__(percent, start, end, accel):
        return(Rotate( angle=interpolate(percent, start.angle, end.angle, accel),
                       origin=(interpolate(percent, start.origin[0], end.origin[0], accel),
                               interpolate(percent, start.origin[1], end.origin[1], accel)) 
                       ))
    
    def __matrices__(self):
        return (                                 
                Matrix(matrix=( 1.0, 0.0,
                                0.0, 1.0,
                                self.origin[0] - self.layer.position[0],
                                self.origin[1] - self.layer.position[0] )),
                
                Matrix(matrix=( cos(self.angle), sin(self.angle),
                                -sin(self.angle), cos(self.angle),
                                0.0,
                                0.0 )),
                                
                Matrix(matrix=( 1.0, 0.0,
                                0.0, 1.0,
                                self.layer.position[0] - self.origin[0],
                                self.layer.position[0] - self.origin[1] ))
                )

## Stretch
#
# Stretch by matrix transformation.
#
class Stretch(Transform):
    
    ## Factor to stretch by, in a 2-tuple formatted (x, y)
    factor = (1.0, 1.0)
    
    ## Origin, in a 2-tuple formatted (x, y)
    origin = (0.0, 0.0)
    
    @staticmethod
    def __interpolate__(percent, start, end, accel):
        return(Stretch(factor=(interpolate(percent, start.factor[0], end.factor[0], accel),
                               interpolate(percent, start.factor[1], end.factor[1], accel)),
                       origin=(interpolate(percent, start.origin[0], end.origin[0], accel),
                               interpolate(percent, start.origin[1], end.origin[1], accel)) 
                       ))
    
    def __matrices__(self):
        return (                                                 
                Matrix(matrix=( 1.0, 0.0,
                                0.0, 1.0,
                                self.origin[0] - self.layer.position[0],
                                self.origin[1] - self.layer.position[1])),
                
                Matrix(matrix=( self.factor[0], 0.0,
                                0.0, 1.0,
                                0.0,
                                0.0 )),

                Matrix(matrix=( 1.0, 0.0,
                                0.0, self.factor[1],
                                0.0,
                                0.0 )),
                                
                Matrix(matrix=( 1.0, 0.0,
                                0.0, 1.0,
                                self.layer.position[0] - self.origin[0],
                                self.layer.position[1] - self.origin[1] ))
                )

## Shear
#
# Shear by matrix transformation.
#
class Shear(Transform):
    
    ## Factor to shear by, in a 2-tuple formatted (x, y)
    factor = (0.0, 0.0)
    
    ## Origin, in a 2-tuple formatted (x, y)
    origin = (0.0, 0.0)
    
    @staticmethod
    def __interpolate__(percent, start, end, accel):
        return(Shear(factor=(interpolate(percent, start.factor[0], end.factor[0], accel),
                             interpolate(percent, start.factor[1], end.factor[1], accel)),
                     origin=(interpolate(percent, start.origin[0], end.origin[0], accel),
                             interpolate(percent, start.origin[1], end.origin[1], accel)) 
                       ))
    
    def __matrices__(self):
        return (                                 
                Matrix(matrix=( 1.0, 0.0,
                                0.0, 1.0,
                                self.origin[0] - self.layer.position[0],
                                self.origin[1] - self.layer.position[1] )),
                
                Matrix(matrix=( 1.0, self.factor[1],
                                0.0, 1.0,
                                0.0,
                                0.0 )),
                
                Matrix(matrix=( 1.0, 0.0,
                                self.factor[0], 1.0,
                                0.0,
                                0.0 )),
                                
                Matrix(matrix=( 1.0, 0.0,
                                0.0, 1.0,
                                self.layer.position[0] - self.origin[0],
                                self.layer.position[1] - self.origin[1] ))
                )

## Clip
#
# Clip by path.
#
class Clip(Element):
    
    precedence = -1.0
    
    ## Path name
    path = None
    
    def __render__(self):
        self.path.context = self.context
        self.path.__render__()
        self.context.clip()


## Mask
#
# Mask by pattern.
#
class Mask(Element):
    
    precedence = -1.0
    
    ## Pattern name
    pattern = None
    
    def __render__(self):
        self.pattern.context = self.context        
        self.context.mask(self.pattern.__render__())

## Pattern source
#
# Set the pattern for the layer.
#
class PatternSource(Element):
    
    precedence = 1.0
    
    ## Pattern name
    pattern = None
    
    def __render__(self):
        self.context.set_source(self.pattern.__render__())
    
    @staticmethod
    def __interpolate__(percent, start, end, accel):
        start.pattern.size = start.size
        end.pattern.size = end.size
        return PatternSource(pattern=Pattern.__interpolate__(percent, start.pattern, end.pattern, accel))